import UIKit
import Foundation
import CommonCrypto

class DWSplitDownloadManager: NSObject {

    static let `default`: DWSplitDownloadManager = DWSplitDownloadManager()
    
    var folderPath : String = NSHomeDirectory() + "/Documents/DWSplitDownloadFolder/"
    
    var splitPath : String = NSHomeDirectory() + "/Documents/DWSplitDownloadFolder/Split/"
    
    private var downloaderDic : [String : DWSplitDownloader] = [:]
    
    private lazy var operationSemap: DispatchSemaphore = {
        let semap = DispatchSemaphore(value: 0)
        semap.signal()
        return semap
    }()
    
    private lazy var queue: OperationQueue = {
        let _queue : OperationQueue = OperationQueue()
        _queue.maxConcurrentOperationCount = 6
        return _queue
    }()
    
    init(_ _folderPath : String? = nil,_ _splitPath : String? = nil) {
        super.init()
        if let _folder = _folderPath {
            self.folderPath = _folder
        }
        if let _split = _splitPath {
            self.splitPath = _split
        }
        if !FileManager.default.fileExists(atPath: self.folderPath) {
            try? FileManager.default.createDirectory(atPath: self.folderPath, withIntermediateDirectories: true)
        }
        if !FileManager.default.fileExists(atPath: self.splitPath) {
            try? FileManager.default.createDirectory(atPath: self.splitPath, withIntermediateDirectories: true)
        }
    }
    
    func download(_ fileStr : String, progressBlock: ((Int) -> ())? = nil, completionBlock: ((Result<(String), DWSplitDownloadError>) -> ())? = nil) {
        let key : String = fileStr.dw_down_md5()
        if findDownloader(fileStr: key) == nil {
            let downloader = DWSplitDownloader.init(fileStr, self.folderPath, self.splitPath, progressBlock: progressBlock, completion: completionBlock)
            downloader.completionBlock = {
                if let error = downloader.error {
                    downloader.completion?(.failure(error))
                }else {
                    downloader.completion?(.success(downloader.splitDownloadModel.path))
                }
            }
            saveDownloader(key: key, downloader: downloader)
            queue.addOperation(downloader)
        }
    }
    
    @discardableResult
    func start(_ url : String) -> Bool {
        if let downloader = findDownloader(fileStr: url.dw_down_md5()) {
            self.deleteDownloader(key: url.dw_down_md5())
            self.download(downloader.fileStr, progressBlock: downloader.progressBlock, completionBlock: downloader.completion)
            return true
        }
        return false
    }
    
    @discardableResult
    func cancel(_ url : String) -> Bool {
        if let downloader = findDownloader(fileStr: url.dw_down_md5()) {
            downloader.cancel()
            return true
        }
        return false
    }
    
    @discardableResult
    func delete(_ url : String) -> Bool {
        if let downloader = findDownloader(fileStr: url.dw_down_md5()) {
            downloader.cancel()
            downloaderDic.removeValue(forKey: url.dw_down_md5())
            try? FileManager.default.removeItem(atPath: downloader.splitDownloadModel.path)
            try? FileManager.default.removeItem(atPath: downloader.splitDownloadModel.splitPath)
            return true
        }
        return false
    }
    
    func cancelAll() {
        for downloader in downloaderDic.values {
            downloader.cancel()
        }
    }
    
    func deleteAllTask() {
        for downloader in downloaderDic.values {
            downloader.cancel()
            try? FileManager.default.removeItem(atPath: downloader.splitDownloadModel.path)
            try? FileManager.default.removeItem(atPath: downloader.splitDownloadModel.splitPath)
        }
        downloaderDic.removeAll()
    }
    
    func saveDownloader(key : String, downloader : DWSplitDownloader) {
        operationSemap.wait()
        downloaderDic.updateValue(downloader, forKey: key)
        operationSemap.signal()
    }
    
    func deleteDownloader(key : String) {
        operationSemap.wait()
        downloaderDic.removeValue(forKey: key)
        operationSemap.signal()
    }
    
    func findDownloader(fileStr : String) -> DWSplitDownloader? {
        operationSemap.wait()
        var downloader : DWSplitDownloader?
        downloader = downloaderDic[fileStr]
        operationSemap.signal()
        return downloader
    }
    
}

extension String {
    
    func dw_down_md5() -> String {
        let cCharArray = self.cString(using: .utf8)
        var uint8Array = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
        CC_MD5(cCharArray, CC_LONG(cCharArray!.count - 1), &uint8Array)
        return (uint8Array.reduce("") { $0 + String(format: "%02X", $1)}).lowercased()
    }
    
}

extension URL {
    
    func dw_down_extendedAttribute(forName name: String) throws -> Data  {
        let data = try withUnsafeFileSystemRepresentation { fileSystemPath -> Data in
            let length = getxattr(fileSystemPath, name, nil, 0, 0, 0)
            guard length >= 0 else { throw URL.dw_down_posixError(errno) }
            var data = Data(count: length)
            let result =  data.withUnsafeMutableBytes { [count = data.count] in
                getxattr(fileSystemPath, name, $0.baseAddress, count, 0, 0)
            }
            guard result >= 0 else { throw URL.dw_down_posixError(errno) }
            return data
        }
        return data
    }
    
    func dw_down_setExtendedAttribute(data: Data, forName name: String) throws {
        try withUnsafeFileSystemRepresentation { fileSystemPath in
            let result = data.withUnsafeBytes {
                setxattr(fileSystemPath, name, $0.baseAddress, data.count, 0, 0)
            }
            guard result >= 0 else { throw URL.dw_down_posixError(errno) }
        }
    }
    
    func dw_down_removeExtendedAttribute(forName name: String) throws {
        try withUnsafeFileSystemRepresentation { fileSystemPath in
            let result = removexattr(fileSystemPath, name, 0)
            guard result >= 0 else { throw URL.dw_down_posixError(errno) }
        }
    }
    
    func dw_down_listExtendedAttributes() throws -> [String] {
        let list = try withUnsafeFileSystemRepresentation { fileSystemPath -> [String] in
            let length = listxattr(fileSystemPath, nil, 0, 0)
            guard length >= 0 else { throw URL.dw_down_posixError(errno) }
            var namebuf = Array<CChar>(repeating: 0, count: length)
            let result = listxattr(fileSystemPath, &namebuf, namebuf.count, 0)
            guard result >= 0 else { throw URL.dw_down_posixError(errno) }
            let list = namebuf.split(separator: 0).compactMap {
                $0.withUnsafeBufferPointer {
                    $0.withMemoryRebound(to: UInt8.self) {
                        String(bytes: $0, encoding: .utf8)
                    }
                }
            }
            return list
        }
        return list
    }
    
    private static func dw_down_posixError(_ err: Int32) -> NSError {
        return NSError(domain: NSPOSIXErrorDomain, code: Int(err), userInfo: [NSLocalizedDescriptionKey: String(cString: strerror(err))])
    }
}
